%% @author Christian Haberland
%%
%% @doc records

-module(graph).
-include_lib("graph.hrl").

%% ====================================================================
%% API functions
%% ====================================================================
-export([
        new/0,
        addVertex/2,
        deleteVertex/2,
        addEdgeU/3,
        addEdgeD/3,
        deleteEdge/3,

        isEmpty/1,
        getAdjacent/2,
        getIncident/2,
        getSource/2,
        getTarget/2,
        getVertexes/1,
        getEdges/1,
        getValE/3,
        getStrE/3,
        getValV/3,
        getStrV/3,
        getAttrV/2,
        getAttrE/2,
        
        setValE/4,
        setStrE/4,
        setValV/4,
        setStrV/4
]).

%% TODO Test ob ID bereits vorhanden
-define(SEED_GRAPH, 2200).
-define(SEED_VERTEX, 4500).
-define(SEED_EDGE, 222200).

%% ====================================================================
%% External exports
%% ====================================================================

new() ->
    #graph{id = random:uniform(?SEED_GRAPH), vertexes = orddict:new(), edges = orddict:new()}.

addVertex(Graph, Vertex) ->
    V = #vertex{id = random:uniform(?SEED_VERTEX), name = Vertex, attr = orddict:new()},
    F = fun(_, Value) -> [{ _, _, Name, _}] = Value, Name =:= V#vertex.name end,
    case orddict:filter(F, Graph#graph.vertexes) of
        []   -> G1 = Graph#graph{vertexes = orddict:append(V#vertex.id, V, Graph#graph.vertexes)},
                {G1, V#vertex.id};
        [{Vertex_ID, [{_, _, _, _}]}] ->  {Graph, Vertex_ID}
    end.

deleteVertex(Graph, Vertex_ID) ->
    case orddict:find(Vertex_ID, Graph#graph.vertexes) of
        {ok, _} -> Edges = getIncident(Graph, Vertex_ID),
                   G1 = Graph#graph{vertexes = orddict:erase(Vertex_ID, Graph#graph.vertexes)},
                   deleteEdges(G1, Edges);
        error   -> Graph
    end.

addEdgeU(Graph, Vertex1_ID, Vertex2_ID) ->
    case findVertexes(Graph, Vertex1_ID, Vertex2_ID) of
        true ->  Random = getEdgeSeed(),
                          Edge = #edge{id = Random,
                                       type = undirected,
                                       source = Vertex1_ID,
                                       target = Vertex2_ID,
                                       attr = orddict:new()
                                      },
                          G1 = Graph#graph{edges = orddict:append(
                                                     Random,
                                                     Edge,
                                                     Graph#graph.edges
                                                                 )
                                          },
                          {G1, Random};
        false -> {Graph, false}
    end.

addEdgeD(Graph, Vertex1_ID, Vertex2_ID) ->
    case findVertexes(Graph, Vertex1_ID, Vertex2_ID) of
        true -> Random = getEdgeSeed(),
                Edge = #edge{id = Random,
                             type = directed,
                             source = Vertex1_ID,
                             target = Vertex2_ID,
                             attr = orddict:new()
                            },
                G1 = Graph#graph{edges = orddict:append(
                                           Random,
                                           Edge,
                                           Graph#graph.edges
                                                       )
                                },
                {G1, Random};
        false -> {Graph, false}
    end.

deleteEdge(Graph, Vertex1_ID, Vertex2_ID) ->
    UndirectedMatcher = fun ({_, [{edge, _, undirected, V1, V2, _}]}) when V1 == Vertex1_ID, V2 == Vertex2_ID -> false ; (_) -> true end,
    UndirectedMatcher2 = fun ({_, [{edge, _, undirected, V2, V1, _}]}) when V1 == Vertex1_ID, V2 == Vertex2_ID -> false ; (_) -> true end,
    DirectedMatcher = fun ({_, [{edge, _, directed, V1, V2, _}]}) when V1 == Vertex1_ID, V2 == Vertex2_ID -> false ; (_) -> true end,
    
    G1 =  Graph#graph{edges = [L || L <- Graph#graph.edges, UndirectedMatcher(L)] },
    G2 =  G1#graph{edges = [L || L <- G1#graph.edges, UndirectedMatcher2(L)] },
    G2#graph{edges = [L || L <- G2#graph.edges, DirectedMatcher(L)] }.

%% Selektoren:
isEmpty(Graph) ->
    lists:flatlength(Graph#graph.vertexes)=:=0.

getSource(Graph, Edge_ID) ->
    TargetEdge = getElement(Graph#graph.edges, Edge_ID),
    TargetEdge#edge.source.
    
getTarget(Graph, Edge_ID) ->
    TargetEdge = getElement(Graph#graph.edges, Edge_ID),
    TargetEdge#edge.target.

getIncident(Graph, Vertex_ID) ->
    CurrentVertex = getElement(Graph#graph.vertexes, Vertex_ID),
    
    UnDirectedMatcher1 = fun ({_, [{edge, _, undirected, V1,_ , _}]}) when V1 == CurrentVertex#vertex.id -> true ; (_) -> false end,
    UnDirectedMatcher2 = fun ({_, [{edge, _, undirected, _, V1, _}]}) when V1 == CurrentVertex#vertex.id -> true ; (_) -> false end,
    DirectedMatcher = fun ({_, [{edge, _, directed, V1,_ , _}]}) when V1 == CurrentVertex#vertex.id -> true ; (_) -> false end,
    
    UndirectEdges = [L || L <- Graph#graph.edges, UnDirectedMatcher1(L)],
    AllUndirectEdges = lists:merge([L || L <- Graph#graph.edges, UnDirectedMatcher2(L)], UndirectEdges),
    Edges = lists:merge([L || L <- Graph#graph.edges, DirectedMatcher(L)], AllUndirectEdges),
    orddict:fetch_keys(Edges).

getAdjacent(Graph, Vertex_ID) ->
    CurrentVertex = getElement(Graph#graph.vertexes, Vertex_ID),
    
    UnDirectedMatcher1 = fun ({_, [{edge, _, undirected, V1,_ , _}]}) when V1 == CurrentVertex#vertex.id -> true ; (_) -> false end,
    UnDirectedMatcher2 = fun ({_, [{edge, _, undirected, _, V1, _}]}) when V1 == CurrentVertex#vertex.id -> true ; (_) -> false end,
    DirectedMatcher = fun ({_, [{edge, _, directed, V1,_ , _}]}) when V1 == CurrentVertex#vertex.id -> true ; (_) -> false end,
    
    UndirectEdges = [L || L <- Graph#graph.edges, UnDirectedMatcher1(L)],
    AllUndirectEdges = lists:merge([L || L <- Graph#graph.edges, UnDirectedMatcher2(L)], UndirectEdges),
    Edges = lists:merge([L || L <- Graph#graph.edges, DirectedMatcher(L)], AllUndirectEdges),
    getAjacentNodes(Edges, CurrentVertex#vertex.id).
    
getVertexes(Graph) ->
    orddict:fetch_keys(Graph#graph.vertexes).

getEdges(Graph) ->
    orddict:fetch_keys(Graph#graph.edges).

getValE(Graph, Edge_ID, Attribut) ->
    TargetEdge = getElement(Graph#graph.edges, Edge_ID),
    case orddict:find(Attribut, TargetEdge#edge.attr) of
        {ok, Value} -> [Result] = Value,Result;
        error       -> false
    end.
    
getValV(Graph, Vertex_ID, Attribut) ->
    TargetVertex = getElement(Graph#graph.vertexes, Vertex_ID),
    case orddict:find(Attribut, TargetVertex#vertex.attr) of
        {ok, Value} -> [Result] = Value,Result;
        error       -> getAttrNameV(Attribut, TargetVertex)
    end.

getAttrNameV(Attribut, Vertex) when Attribut =:= name ->
    Vertex#vertex.name.

getStrE(Graph, Edge_ID, Attribut) ->
    getValE(Graph, Edge_ID, Attribut).
                                   
getStrV(Graph, Vertex_ID, Attribut) ->
    getValV(Graph, Vertex_ID, Attribut).

getAttrV(Graph, Vertex_ID) -> 
    TargetVertex = getElement(Graph#graph.vertexes, Vertex_ID),
    TargetVertex#vertex.attr.
    
getAttrE(Graph, Edge_ID) -> 
    TargetEdge = getElement(Graph#graph.edges, Edge_ID),
    TargetEdge#edge.attr.

%% Mutator
setValE(Graph, Edge_ID, Attribut, Value) ->
    setStrE(Graph, Edge_ID, Attribut, Value).
    
setStrE(Graph, Edge_ID, Attribut, Value) ->
    TargetEdge = getElement(Graph#graph.edges, Edge_ID),
    G1 = deleteEdge(Graph, TargetEdge#edge.source, TargetEdge#edge.target),
    case orddict:find(Attribut, TargetEdge#edge.attr) of
        {ok, _} -> Tmp = orddict:erase(Attribut, TargetEdge#edge.attr),
                   NewAttribs = orddict:append(Attribut, Value, Tmp);
        error   -> NewAttribs = orddict:append(Attribut, Value, TargetEdge#edge.attr)
    end,
    G1#graph{edges = orddict:append(
                       TargetEdge#edge.id,
                       TargetEdge#edge{attr = NewAttribs},
                       G1#graph.edges
                                 )
            }.

setValV(Graph, Vertex_ID, Attribut, Value) ->
    setStrV(Graph, Vertex_ID, Attribut, Value).

setStrV(Graph, Vertex_ID, Attribut, Value) ->
    TargetVertex = getElement(Graph#graph.vertexes, Vertex_ID),
    G1 = deleteVertex(Graph, TargetVertex#vertex.id),
    
    case orddict:find(Attribut, TargetVertex#vertex.attr) of
        {ok, _} -> Tmp = orddict:erase(Attribut, TargetVertex#vertex.attr),
                   NewAttribs = orddict:append(Attribut, Value, Tmp);
        error   -> NewAttribs = orddict:append(Attribut, Value, TargetVertex#vertex.attr)
    end,
    G1#graph{vertexes = orddict:append(
                          TargetVertex#vertex.id,
                          TargetVertex#vertex{attr = NewAttribs},
                          G1#graph.vertexes
                                      )
            }.

%% ====================================================================
%% Internal functions
%% ====================================================================

deleteEdges(Graph, []) ->
    Graph;
deleteEdges(Graph, Edges) ->
    [Edge_ID|T] = Edges,
    CurrentEdge = getElement(Graph#graph.edges, Edge_ID),
    G1 = deleteEdge(Graph, CurrentEdge#edge.source, CurrentEdge#edge.target),
    deleteEdges(G1, T).

getAjacentNodes([], _) ->
    [];
getAjacentNodes(Edges, Vertex_ID) ->
    [{_, EdgeList}|T] = Edges,
    [Edge] = EdgeList,
    case Edge#edge.type of
        
        directed -> if
                        Edge#edge.source =:= Vertex_ID -> lists:append(
                                                            [Edge#edge.target],
                                                            getAjacentNodes(T, Vertex_ID)
                                                                      );
                        true                           -> getAjacentNodes(T, Vertex_ID)
                    end;
        undirected -> if
                          Edge#edge.source =:= Vertex_ID -> lists:append(
                                                              [Edge#edge.target],
                                                              getAjacentNodes(T, Vertex_ID)
                                                                        );
                          Edge#edge.target =:= Vertex_ID -> lists:append(
                                                              [Edge#edge.source],
                                                              getAjacentNodes(T, Vertex_ID)
                                                                        );
                          true                           -> getAjacentNodes(T, Vertex_ID)
                      end
    end.

getEdgeSeed() ->
    random:uniform(?SEED_EDGE).

findVertexes(Graph, Vertex1_ID, Vertex2_ID) ->
    case orddict:find(Vertex1_ID, Graph#graph.vertexes) of
        {ok, _} -> case orddict:find(Vertex2_ID, Graph#graph.vertexes) of
                       {ok, _} -> true;
                       error   -> false
                   end;
        error   -> false
    end.

getElement(Elements, Element) ->
    case orddict:find(Element, Elements) of
        {ok, Value} -> [RetVal] = Value, RetVal;
        error       -> false
    end.
